
/*
 * Name: elfinfo
 * Version: 0.1.2
 * Description: Utility to print information about ELF files
 * Dependencies: bzbutil, bzbfile
 */

#include "balzebub.h"
#include <unistd.h>

using namespace bzbterm;
using namespace bzbio;
using namespace bzbshell;
using namespace bzbfile;

EscSeq TITLE_COLOR = fg::bright_yellow;
EscSeq DATA_COLOR = fg::bright_cyan;
EscSeq HEADER_COLOR = fg::blue;

constexpr int VERSION_MAJOR = 0;
constexpr int VERSION_MINOR = 1;
constexpr int VERSION_PATCH = 3;

const char* PROGNAME = "elfinfo";


void print_version_exit() {
	std::cout << PROGNAME << " v";
	std::cout << VERSION_MAJOR << "." <<
				 VERSION_MINOR << "." <<
				 VERSION_PATCH << std::endl;
	std::cout << "This program is part of the Balzebub project." << std::endl;
	exit(0);
}


void print_help_exit(int err = 0) {
	println("Usage: ", PROGNAME, " <filename> [ -no-color ]");
	println("Show information about an ELF file");
	println("-no-color\tPrint information without color escape codes");
	exit(err);
}


template<typename T>
void print_elf_info(T elf_file) {

	print(TITLE_COLOR, "Class: ");
	if(elf_file.e_ident[ELF_EI_CLASS] == ELF_CLASS_32) {
		println(DATA_COLOR, "ELF32");
	} else if(elf_file.e_ident[ELF_EI_CLASS] == ELF_CLASS_64) {
		println(DATA_COLOR, "ELF64");
	} else {
		println(DATA_COLOR, "Unknown");
	}


	print(TITLE_COLOR, "Endianness: ");
	print(DATA_COLOR);
	if(elf_file.e_ident[ELF_EI_DATA] == ELF_LITTLE_ENDIAN) {
		print("Little Endian");
	} else if (elf_file.e_ident[ELF_EI_DATA] == ELF_BIG_ENDIAN) {
		print("Big Endian");
	} else {
		print("Unknown");
	}
	c_println();

	print(TITLE_COLOR, "ELF version: ");
	println(DATA_COLOR, (uint32_t) elf_file.e_ident[ELF_EI_VERSION]);

	print(TITLE_COLOR, "Target system: ");
	print(DATA_COLOR);
	switch(elf_file.e_ident[ELF_EI_OSABI]) {
		case ELF_ABI_SYSTEM_V:			print("UNIX - System V (default)"); break;
		case ELF_ABI_HP_UX:				print("HP-UX"); break;
		case ELF_ABI_NETBSD:			print("NetBSD"); break;
		case ELF_ABI_LINUX:				print("Linux"); break;
		case ELF_ABI_GNUHURD:			print("GNU Hurd"); break;
		case ELF_ABI_SOLARIS:			print("Solaris"); break;
		case ELF_ABI_AIX:				print("AIX"); break;
		case ELF_ABI_IRIX:				print("IRIX"); break;
		case ELF_ABI_FREEBSD:			print("FreeBSD"); break;
		case ELF_ABI_TRU64:				print("Tru64"); break;
		case ELF_ABI_NOVELL_MODESTO:	print("Novell Modesto"); break;
		case ELF_ABI_OPENBSD:			print("OpenBSD"); break;
		case ELF_ABI_OPENVMS:			print("OpenVMS"); break;
		case ELF_ABI_NONSTOP_KERNEL:	print("NonStop Kernel"); break;
		case ELF_ABI_AROS:				print("AROS"); break;
		case ELF_ABI_FENIXOS:			print("FenixOS"); break;
		case ELF_ABI_CLOUDABI:			print("CloudABI"); break;
		default:						print("Unknown"); break;
	}
	c_println();

	print(TITLE_COLOR, "ABI version: ");
	println(DATA_COLOR, (uint32_t) elf_file.e_ident[ELF_EI_ABIVERSION]);

	print(TITLE_COLOR, "Type:", " ");
	print(DATA_COLOR);
	switch(elf_file.e_type) {
		case ELF_ET_NONE: 	print("NONE (None)"); break;
		case ELF_ET_REL: 	print("REL (Relocatable file)"); break;
		case ELF_ET_EXEC: 	print("EXEC (Executable file)"); break;
		case ELF_ET_DYN: 	print("DYN (Shared object file)"); break;
		case ELF_ET_CORE: 	print("CORE (Core file)"); break;
		case ELF_ET_LOOS: 	print("LOOS (Operating system-specific)"); break;
		case ELF_ET_HIOS: 	print("HIOS (Operating system-specific)"); break;
		case ELF_ET_LOPROC: print("LOPROC (Processor-specific)"); break;
		case ELF_ET_HIPROC: print("HIPROC (Processor-specific)"); break;
		default: 			print("Unknown "); break;
	}
	c_println();

	print(TITLE_COLOR, "Target machine: ");
	print(DATA_COLOR);
	switch(elf_file.e_machine) {
		case ELF_ARCH_UNSPECIFIED:	print("Unspecified"); break;
		case ELF_ARCH_SPARC:		print("SPARC"); break;
		case ELF_ARCH_X86:			print("x86"); break;
		case ELF_ARCH_MIPS:			print("MIPS"); break;
		case ELF_ARCH_POWERPC:		print("PowerPC"); break;
		case ELF_ARCH_S390:			print("S390"); break;
		case ELF_ARCH_ARM:			print("ARM"); break;
		case ELF_ARCH_SUPERH:		print("SuperH"); break;
		case ELF_ARCH_IA_64:		print("IA-64"); break;
		case ELF_ARCH_X86_64:		print("x86-64"); break;
		case ELF_ARCH_AARCH64:		print("AArch64"); break;
		case ELF_ARCH_RISC_V:		print("RISC-V"); break;
		default:					print("Unknown (0x", std::hex,
										  elf_file.e_machine,
										  std::dec, ")");
	}
	c_println();

	print(TITLE_COLOR, "Version: ");
	if(elf_file.e_version == 1) {
		println(DATA_COLOR, "1 (current)");
	} else {
		println(DATA_COLOR, elf_file.e_version, " (invalid)");
	}

	print(TITLE_COLOR, "Entry point: ");
	println(DATA_COLOR, "0x", std::hex, elf_file.e_entry, std::dec);

	print(TITLE_COLOR, "Program header offset: ");
	println(DATA_COLOR, elf_file.e_phoff);

	print(TITLE_COLOR, "Section header offset: ");
	println(DATA_COLOR, elf_file.e_shoff);

	print(TITLE_COLOR, "Flags: ");
	println(DATA_COLOR, std::hex, "0x", elf_file.e_flags, std::dec);

	print(TITLE_COLOR, "ELF header size: ");
	println(DATA_COLOR, elf_file.e_ehsize);

	print(TITLE_COLOR, "Program header entry size: ");
	println(DATA_COLOR, elf_file.e_phentsize);

	print(TITLE_COLOR, "Number of program headers: ");
	println(DATA_COLOR, elf_file.e_phnum);

	print(TITLE_COLOR, "Section header entry size: ");
	println(DATA_COLOR, elf_file.e_shentsize);

	print(TITLE_COLOR, "Number of section headers: ");
	println(DATA_COLOR, elf_file.e_shnum);

	print(TITLE_COLOR, "Section headers string table index: ");
	println(DATA_COLOR, elf_file.e_shstrndx);

	println(TITLE_COLOR, "Interpreter: ", DATA_COLOR, elf_file.interpreter);

	for(size_t i = 0; i < elf_file.prog_headers.size(); i++) {

		println(HEADER_COLOR, "\nProgram header number ", i);

		print(TITLE_COLOR, "Type:", " ");
		print(DATA_COLOR);
		switch(elf_file.prog_headers[i].p_type) {
			case ELF_PT_NULL: 			print("NULL (Unused)"); break;
			case ELF_PT_LOAD: 			print("LOAD (Loadable segment)"); break;
			case ELF_PT_DYNAMIC: 		print("DYNAMIC (Dynamic linking information)"); break;
			case ELF_PT_INTERP: 		print("INTERP (Interpreter)"); break;
			case ELF_PT_NOTE: 			print("NOTE (Notes)"); break;
			case ELF_PT_SHLIB: 			print("SHLIB (Invalid)"); break;
			case ELF_PT_PHDR: 			print("PHDR (Program header pointer)"); break;
			case ELF_PT_LOOS: 			print("LOOS (System specific)"); break;
			case ELF_PT_HIOS: 			print("HIOS (System specific)"); break;
			case ELF_PT_LOPROC: 		print("LOPROC (Processor specific)"); break;
			case ELF_PT_HIPROC: 		print("HIPROC (Processor specific)"); break;
			case ELF_PT_GNU_EH_FRAME: 	print("GNU_EH_FRAME"); break;
			case ELF_PT_GNU_STACK: 		print("GNU_STACK"); break;
			case ELF_PT_GNU_RELRO: 		print("GNU_RELRO"); break;
			default: 					print("Unknown (0x", std::hex,
										  elf_file.prog_headers[i].p_type,
										  std::dec, ")");
		}
		c_println();

		print(TITLE_COLOR, "Offset: ");
		println(DATA_COLOR, elf_file.prog_headers[i].p_offset);

		print(TITLE_COLOR, "Virtual Address: ");
		println(DATA_COLOR, "0x", std::hex, elf_file.prog_headers[i].p_vaddr, std::dec);

		print(TITLE_COLOR, "Physical Address: ");
		println(DATA_COLOR, "0x", std::hex, elf_file.prog_headers[i].p_paddr, std::dec);

		print(TITLE_COLOR, "Segment size in the image file (may be 0): ");
		println(DATA_COLOR, elf_file.prog_headers[i].p_filesz);

		print(TITLE_COLOR, "Segment size in memory (may be 0): ");
		println(DATA_COLOR, elf_file.prog_headers[i].p_memsz);

		print(TITLE_COLOR, "Alignment: ");
		println(DATA_COLOR, "0x", std::hex, elf_file.prog_headers[i].p_align, std::dec);

		print(TITLE_COLOR, "Flags: ");
		print(DATA_COLOR);
		if(elf_file.prog_headers[i].p_flags & ELF_PH_FLAG_READ) {
			print("R");
		} else {
			print("-");
		}

		if(elf_file.prog_headers[i].p_flags & ELF_PH_FLAG_WRITE) {
			print("W");
		} else {
			print("-");
		}

		if(elf_file.prog_headers[i].p_flags & ELF_PH_FLAG_EXEC) {
			print("X");
		} else {
			print("-");
		}
		c_println();

	}

	for(size_t i = 0; i < elf_file.sec_headers.size(); i++) {

		println(HEADER_COLOR, "\nSection header number ", i);

		println(TITLE_COLOR, "Name: ", DATA_COLOR, elf_file.sec_headers[i].name);

		println(TITLE_COLOR, "Name offset inside shstrtab: ", DATA_COLOR,
				"0x", std::hex, elf_file.sec_headers[i].sh_name, std::dec);

		print(TITLE_COLOR, "Type: ");
		print(DATA_COLOR);
		switch(elf_file.sec_headers[i].sh_type) {
			case ELF_SHT_NULL: 			print("NULL (Unused)"); break;
			case ELF_SHT_PROGBITS: 		print("PROGBITS (Program data)"); break;
			case ELF_SHT_SYMTAB: 		print("SYMTAB (Symbol table)"); break;
			case ELF_SHT_STRTAB: 		print("STRTAB (String table)"); break;
			case ELF_SHT_RELA: 			print("RELA (Relocation entries with addends)"); break;
			case ELF_SHT_HASH: 			print("HASH (Symbol hash table)"); break;
			case ELF_SHT_DYNAMIC: 		print("DYNAMIC (Dynamic linking information)"); break;
			case ELF_SHT_NOTE: 			print("NOTE (Notes)"); break;
			case ELF_SHT_NOBITS: 		print("NOBITS (Program space with no data, BSS)"); break;
			case ELF_SHT_REL: 			print("REL (Relocation entries without addends)"); break;
			case ELF_SHT_SHLIB: 		print("SHLIB (Reserved)"); break;
			case ELF_SHT_DYNSYM: 		print("DYNSYM (Dynamic linker symbol table)"); break;
			case ELF_SHT_INIT_ARRAY: 	print("INIT_ARRAY (Array of constructors)"); break;
			case ELF_SHT_FINI_ARRAY: 	print("FINI_ARRAY (Array of destructors)"); break;
			case ELF_SHT_PREINIT_ARRAY: print("PREINIT_ARRAY (// Array of pre-constructors)"); break;
			case ELF_SHT_GROUP: 		print("GROUP (Section group)"); break;
			case ELF_SHT_SYMTAB_SHNDX: 	print("SYMTAB_SHNDX (Extended section indices)"); break;
			case ELF_SHT_NUM: 			print("NUM (Number of defined types)"); break;
			case ELF_SHT_LOOS: 			print("LOOS (System specific)"); break;
			case ELF_SHT_HIOS: 			print("HIOS (System specific) or VERSYM (GNU)"); break;
			case ELF_SHT_LOPROC: 		print("LOPROC (Processor specific)"); break;
			case ELF_SHT_HIPROC: 		print("HIPROC (Processor specific)"); break;
			case ELF_SHT_LOUSER: 		print("LOUSER (Application specific)"); break;
			case ELF_SHT_HIUSER: 		print("HIUSER (Application specific)"); break;
			case ELF_SHT_GNU_HASH:		print("GNU_HASH"); break;
			case ELF_SHT_VERNEED:		print("VERNEED"); break;
			default:				print("Unknown (0x", std::hex,
										  elf_file.sec_headers[i].sh_type,
										  std::dec, ")");
		}
		c_println();

		print(TITLE_COLOR, "Flags: ", DATA_COLOR);
		if(elf_file.sec_headers[i].sh_flags & ELF_SHF_WRITE) {
			print("W");
		}
		if(elf_file.sec_headers[i].sh_flags & ELF_SHF_ALLOC) {
			print("A");
		}
		if(elf_file.sec_headers[i].sh_flags & ELF_SHF_EXECINSTR) {
			print("X");
		}
		if(elf_file.sec_headers[i].sh_flags & ELF_SHF_MERGE) {
			print("M");
		}
		if(elf_file.sec_headers[i].sh_flags & ELF_SHF_STRINGS) {
			print("S");
		}
		if(elf_file.sec_headers[i].sh_flags & ELF_SHF_INFO_LINK) {
			print("I");
		}
		if(elf_file.sec_headers[i].sh_flags & ELF_SHF_LINK_ORDER) {
			print("L");
		}
		if(elf_file.sec_headers[i].sh_flags & ELF_SHF_OS_NONCONFORMING) {
			print("O");
		}
		if(elf_file.sec_headers[i].sh_flags & ELF_SHF_GROUP) {
			print("G");
		}
		if(elf_file.sec_headers[i].sh_flags & ELF_SHF_TLS) {
			print("T");
		}
		if(elf_file.sec_headers[i].sh_flags & ELF_SHF_COMPRESSED) {
			print("C");
		}
		if(elf_file.sec_headers[i].sh_flags & ELF_SHF_MASKOS) {
			print("o");
		}
		if(elf_file.sec_headers[i].sh_flags & ELF_SHF_MASKPROC) {
			print("p");
		}
		c_println();

		println(TITLE_COLOR, "Address: ",
				DATA_COLOR, "0x", std::hex, elf_file.sec_headers[i].sh_addr,
				std::dec);

		println(TITLE_COLOR, "Offset: ",
				DATA_COLOR, "0x", std::hex, elf_file.sec_headers[i].sh_offset,
				std::dec);

		println(TITLE_COLOR, "Size: ",
				DATA_COLOR, elf_file.sec_headers[i].sh_size);

		println(TITLE_COLOR, "Link: ",
				DATA_COLOR, elf_file.sec_headers[i].sh_link);

		println(TITLE_COLOR, "Info: ",
				DATA_COLOR, elf_file.sec_headers[i].sh_info);

		println(TITLE_COLOR, "Address alignment: ",
				DATA_COLOR, "0x", std::hex, elf_file.sec_headers[i].sh_addralign,
				std::dec);

		println(TITLE_COLOR, "Entry size: ",
				DATA_COLOR, elf_file.sec_headers[i].sh_entsize);

	}

}


int main(int argc, char const *argv[]) {

	if(argc < 2) {
		print_help_exit(1);
	}

	if(argc == 3) {

		std::string arg3 = argv[2];

		if(arg3 == "-no-color") {
			TITLE_COLOR = EscSeq("");
			DATA_COLOR = EscSeq("");
			HEADER_COLOR = EscSeq("");
		}
	}

	std::string filename = argv[1];

	if(filename == "--help" || filename == "-h") {
		print_help_exit(0);
	}

	if(filename == "--version" || filename == "-v") {
		print_version_exit();
	}

	if(!bzbfile::exists(filename)) {
		println(fg::red, "ERROR This file does not exist");
		exit(2);
	}

	uint8_t arch = bzbfile::get_elf_class(filename);

	if(arch == ELF_CLASS_32) {

		ELF32 elf_file(filename);
		print_elf_info(elf_file);

	} else if(arch == ELF_CLASS_64) {

		ELF64 elf_file(filename);
		print_elf_info(elf_file);

	} else {
		println(fg::red, "ERROR Malformed ELF file (EI_CLASS is uncorrect)");
		exit(3);
	}

	return 0;
}
